/*
 * Copyright (c) 2003-2010, Live Media Pty. Ltd.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted
 * provided that the following conditions are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice, this list of
 *     conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright notice, this list of
 *     conditions and the following disclaimer in the documentation and/or other materials
 *     provided with the distribution.
 *  3. Neither the name of Live Media nor the names of its contributors may be used to endorse
 *     or promote products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.colony.zone.process;

import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import com.argot.TypeException;
import com.argot.TypeLibrary;
import com.argot.remote.MetaObject;
import com.colony.concurrent.ThreadPool;
import com.colony.zone.CRL;
import com.colony.zone.IBoundZoneObject;
import com.colony.zone.IMessageReceiverObject;
import com.colony.zone.IRealm;
import com.colony.zone.IZone;
import com.colony.zone.ZoneException;

public class HashMapZone 
implements IZone
{
	private TypeLibrary _library;
	private CRL _location;
	private IZone _parent;
	private IRealm _realm;
	private ThreadPool _threadPool;
	private boolean _isBound = false;
    private HashMap  _bindings = new HashMap();
    private static final String empty = new String("empty");
	
    public HashMapZone(  )
    {

    }
    
    private class HashtableBinding
    {
       public Object value;
       public int type;
    }

	public void bind(CRL location, IZone parent, TypeLibrary library ) 
	throws ZoneException 
	{
		_location = location;
		_parent = parent;
		_library = library;
		
		if (parent!=null)
		{
			_threadPool = parent.getThreadPool();
			_realm = _parent.getRealm();
		}
		_isBound = true;
	}
	
	public boolean isBound()
	{
		return _isBound;
	}
	
	private void checkBound()
	throws ZoneException
	{
		if (!isBound())
			throw new ZoneException("Zone not bound to parent");
	}
	
	public CRL getLocation() 
	throws ZoneException 
	{
		checkBound();
		return _location;
	}

	public IRealm getRealm() 
	throws ZoneException 
	{
		checkBound();
		return _realm;
	}
	
	public ThreadPool getThreadPool()
	throws ZoneException
	{
		return _threadPool;
	}
	
	public String getName() 
	throws ZoneException 
	{
		checkBound();
		return _location.getName();
	}
	


	public void addZone(String name, IZone zone) 
	throws ZoneException 
	{
		checkBound();
    	Object o = _bindings.get( name );
    	if ( null != o ) throw new ZoneException( "Object name already in use." );

    	try 
    	{
    		put( name, zone);
			zone.bind( new CRL( _location, name), this, _library );
		} 
    	catch (URISyntaxException e) 
		{
			throw new ZoneException( e.getMessage(), e );
		}
	}


	public Object get(String name) 
	throws ZoneException 
	{
		checkBound();
		if ("".equals(name))
			return this;
		HashtableBinding hb = (HashtableBinding) _bindings.get(name);
		if (hb == null )
			throw new ZoneException("not found: " + name );
		if (hb.value == empty)
			return null;
		return hb.value;
	}
	
	public Object getReference(String name)
	throws ZoneException
	{
		throw new ZoneException("not implemented");
	}
	
	public MetaObject getMetaReference(String name)
	throws ZoneException
	{
		checkBound();
		if ("".equals(name))
			throw new ZoneException("invalid name");
		HashtableBinding hb = (HashtableBinding) _bindings.get(name);
		if (hb == null )
			throw new ZoneException("not found: " + name );
		if (hb.value == empty)
			return null;
		
		if (hb.type == -1 )
			throw new ZoneException("Object not typed: " + name + " (" + hb.value.getClass().getName() + ")");
		
		try {
			return new MetaObject( new CRL(_location + "/" + name ), hb.type );
		} catch (URISyntaxException e) {
			throw new ZoneException(e.getMessage(), e);
		}
		//throw new ZoneException("not implemented");
	}

	public int getType(String name) 
	throws ZoneException 
	{
		HashtableBinding hb = (HashtableBinding) _bindings.get(name);
		if (hb.value == empty)
			throw new ZoneException("item not found");
		return hb.type;
	}
		
	public void put(String name, Object data )
	throws ZoneException
	{
		if (name == null || name.length() == 0 )
			throw new ZoneException("invalid name");
		if (data == null)
			throw new ZoneException("data is null");
		
		try {
			int[] ids = _library.getId(data.getClass());
			if (ids.length != 1)
				throw new ZoneException("Data type is ambiguous");
			
			put(name, ids[0], data );
		} catch (TypeException e) {
			System.err.println("Zone: Putting "+ name + ". Class not bound for: " + data.getClass().getName() );
			//throw new ZoneException(e.getMessage(), e);
			put(name, -1, data );
		}
		
	}

	public void put(String name, int type, Object data) 
	throws ZoneException 
	{
		checkBound();
		
		if (name == null || name.length() == 0 )
			throw new ZoneException("name invalid");
		
		if (data == null)
			throw new ZoneException("data is null");
		
    	Object o = _bindings.get(name);
		if (null != o)
			throw new ZoneException("Object name already in use.");

		
		try {
			Class clss = _library.getClass(type);
			if (!clss.isAssignableFrom(data.getClass()))
				throw new ZoneException("Data not assignable to type ("+clss.getName()+" <- "+data.getClass().getName() + ")");
		} catch (TypeException e1) {
			// do nothing for now
		}
		
		if ( data instanceof IBoundZoneObject)
		{
			IBoundZoneObject bound = (IBoundZoneObject) data;
	    	try 
	    	{
				bound.bind( new CRL( _location, name), (IZone) this, _library );
			} 
	    	catch (URISyntaxException e) 
			{
				throw new ZoneException( e.getMessage(), e );
			}			
		}
		
		HashtableBinding hb = new HashtableBinding();
		hb.value = data;
		hb.type = type;
		_bindings.put(name, hb);		
	}

	public Object remove(String name) 
	throws ZoneException 
	{
		checkBound();
	   	Object o = _bindings.remove(name);
		if (null == o)
			return null;
		HashtableBinding hb = (HashtableBinding) o;
		return hb.value;
    }

    public void rename( String name, String newName )
    throws ZoneException
    {
    	checkBound();
        Object o = _bindings.get(name);
		if (null == o)
			throw new ZoneException("Can not rename, object does not exist");
		Object o2 = _bindings.get(newName);
		if (o2 != null)
			throw new ZoneException(
					"Can not rename, object name exists already");
		_bindings.remove(name);
		_bindings.put(newName, o);
    	
    }

	public Object find(CRL location) 
	throws ZoneException 
	{
		checkBound();
		if (_location.isSameBase(location))
		{
			IZone zone = this;
			int localPathPos = _location.getPathSize();
			int locationPathSize = location.getPathSize();
			if (localPathPos == locationPathSize)
			{
				return this;
			}
			Object o = null;
			for (int x=localPathPos;x<locationPathSize;x++)
			{
				String part = location.getPathPart(x);				
				if (part==null) return this;
				o = zone.get(part);
				if (o instanceof IZone && locationPathSize > localPathPos+1 )
				{
					zone = (IZone) o;
				}
				else if (o instanceof IRealm)
				{
					return ((IRealm)o).find(location);
				}
				
			}
			return o;
		}
		if (_realm==null)
		{
			throw new ZoneException("Can't find "+location.toString());
		}		
		return _realm.find(location);
	}	
    
	public void send(CRL location, Object object) 
	throws ZoneException 
	{
		checkBound();
		if (_location.isSameBase(location))
		{
			IZone zone = this;
			int localPathPos = _location.getPathSize();
			int locationPathSize = location.getPathSize();
			for (int x=localPathPos;x<locationPathSize;x++)
			{
				String part = location.getPathPart(x);				
				if (part==null) 
					throw new ZoneException("Unable to send object to "+location.toString());
				Object o = zone.get(part);
				if (o instanceof IZone && locationPathSize > localPathPos+1 )
				{
					zone = (IZone) o;
				}
				else if (o instanceof IRealm)
				{
					((IRealm)o).send(location,object);
					return;
				}
				else if (o instanceof IMessageReceiverObject && locationPathSize > localPathPos+1)
				{
					((IMessageReceiverObject)o).send(object);
					return;
				}
				else
				{
					throw new ZoneException("Unable to send object to "+location.toString());
				}
			}			
		}
		if (_realm==null)
		{
			throw new ZoneException("Can't send to "+location.toString());
		}
		_realm.send(location,object);
	}

	public String[] keys() 
	throws ZoneException 
	{
		String[] keys = new String[ _bindings.size() ];
		return (String[]) _bindings.keySet().toArray(keys);
		//ArrayList list = new ArrayList();
		//Set set = _bindings.keySet();
		//Iterator iter = set.iterator();
		//while(iter.hasNext())
		//{
		//	list.add(iter.next());
		//}
		//return list;
	}





}
